/*-------------------------------------------------------------------------
 * xid8funcs.c
 *
 *	将内部事务 ID 导出到用户级。
 *
 * 请注意，仅顶级事务 ID 会暴露给用户会话。
 * 这很重要，因为 xid8s 经常在全局 xmin 地平线之外存在，或者甚至可能被发送到其他机器，
 * 因此我们不能依靠能够通过 SubTransGetTopmostTransaction() 等函数
 * 将子事务 ID 与其父事务相关联。
 *
 * 这些函数用于支持 txid_XXX 函数以及更新的 pg_current_xact、pg_current_snapshot
 * 和相关的 fmgr 函数，因为它们之间的唯一区别在于它们是将 xid8 还是 int8 值暴露给用户。
 * 最终，txid_XXX 变体应被删除。
 *
 *
 *	Copyright (c) 2003-2022, PostgreSQL Global Development Group
 *	Author: Jan Wieck, Afilias USA INC.
 *	64-bit txids: Marko Kreen, Skype Technologies
 *
 *	src/backend/utils/adt/xid8funcs.c
 *
 *-------------------------------------------------------------------------
 */

#include "postgres.h"

#include "access/clog.h"
#include "access/transam.h"
#include "access/xact.h"
#include "access/xlog.h"
#include "funcapi.h"
#include "lib/qunique.h"
#include "libpq/pqformat.h"
#include "miscadmin.h"
#include "postmaster/postmaster.h"
#include "storage/lwlock.h"
#include "storage/procarray.h"
#include "utils/builtins.h"
#include "utils/memutils.h"
#include "utils/snapmgr.h"
#include "utils/xid8.h"


/*
 * 如果定义，则使用 bsearch() 函数在快照中搜索 xid8s
 * 这些快照有超过指定数量的值。
 */
#define USE_BSEARCH_IF_NXIP_GREATER 30


/*
 * 快照包含FullTransactionIds。
 */
typedef struct
{
	/*
	 * 4字节长度hdr，不应直接触碰。
	 *
	 * 显式嵌入是可以的，因为我们始终希望正确对齐。
	 */
	int32		__varsz;

	uint32		nxip;			/* xip数组中的fxids数量 */
	FullTransactionId xmin;
	FullTransactionId xmax;
	/* 进行中fxids，xmin <= xip[i] < xmax: */
	FullTransactionId xip[FLEXIBLE_ARRAY_MEMBER];
} pg_snapshot;

#define PG_SNAPSHOT_SIZE(nxip) \
	(offsetof(pg_snapshot, xip) + sizeof(FullTransactionId) * (nxip))
#define PG_SNAPSHOT_MAX_NXIP \
	((MaxAllocSize - offsetof(pg_snapshot, xip)) / sizeof(FullTransactionId))

/*
 * 帮助器从64位xid中获取TransactionId，并进行环绕检测。
 *
 * 如果xid在未来，则为错误。否则，返回true，如果
 * 交易仍然足够新，我们可以确定它是否
 * 已提交，否则返回false。如果*extracted_xid不为NULL，则设置
 * 为交易ID的低32位（即实际的XID，没有
 * 纪元信息）。
 *
 * 调用者必须持有XactTruncationLock，因为它涉及任意
 * XIDs，并且必须继续持有该锁，直到完成与这些XIDs相关的任何clog查找。
 */
static bool fc_TransactionIdInRecentPast(FullTransactionId fc_fxid, TransactionId *fc_extracted_xid)
{
	TransactionId fc_xid = XidFromFullTransactionId(fc_fxid);
	uint32		fc_now_epoch;
	TransactionId fc_now_epoch_next_xid;
	FullTransactionId fc_now_fullxid;
	TransactionId fc_oldest_xid;
	FullTransactionId fc_oldest_fxid;

	fc_now_fullxid = ReadNextFullTransactionId();
	fc_now_epoch_next_xid = XidFromFullTransactionId(fc_now_fullxid);
	fc_now_epoch = EpochFromFullTransactionId(fc_now_fullxid);

	if (fc_extracted_xid != NULL)
		*fc_extracted_xid = fc_xid;

	if (!TransactionIdIsValid(fc_xid))
		return false;

	/* 对于非正常交易ID，我们可以忽略纪元。 */
	if (!TransactionIdIsNormal(fc_xid))
		return true;

	/* 如果交易ID在未来，抛出错误。 */
	if (!FullTransactionIdPrecedes(fc_fxid, fc_now_fullxid))
		ereport(ERROR,
				(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
				 errmsg("transaction ID %llu is in the future",
						(unsigned long long) U64FromFullTransactionId(fc_fxid))));

	/*
	 * ShmemVariableCache->oldestClogXid受XactTruncationLock保护，
	 * 但我们在这里不获取该锁。相反，我们要求调用者
	 * 获取它，因为调用者大概会查找返回的XID。
	 * 如果我们在这个函数内获取和释放了锁，则在调用者完成对XID的处理之前，可能会导致CLOG截断。
	 */
	Assert(LWLockHeldByMe(XactTruncationLock));

	/*
	 * 如果fxid不比ShmemVariableCache->oldestClogXid旧，则
	 * 相关的CLOG条目 guaranteed 仍然存在。将
	 * ShmemVariableCache->oldestClogXid转换为FullTransactionId以便与fxid进行比较。
	 * 确定正确的纪元，因为我们知道oldest_fxid
	 * 不应比now_fullxid旧2^31。
	 */
	fc_oldest_xid = ShmemVariableCache->oldestClogXid;
	Assert(TransactionIdPrecedesOrEquals(fc_oldest_xid, fc_now_epoch_next_xid));
	if (fc_oldest_xid <= fc_now_epoch_next_xid)
	{
		fc_oldest_fxid = FullTransactionIdFromEpochAndXid(fc_now_epoch, fc_oldest_xid);
	}
	else
	{
		Assert(fc_now_epoch > 0);
		fc_oldest_fxid = FullTransactionIdFromEpochAndXid(fc_now_epoch - 1, fc_oldest_xid);
	}
	return !FullTransactionIdPrecedes(fc_fxid, fc_oldest_fxid);
}

/*
 * 将从调用者持有的快照中获得的TransactionId转换为
 * FullTransactionId。将next_fxid作为参考FullTransactionId，这样
 * 我们就可以计算高位。它必须是调用者在快照创建后通过
 * ReadNextFullTransactionId()获得的。
 */
static FullTransactionId
fc_widen_snapshot_xid(TransactionId fc_xid, FullTransactionId fc_next_fxid)
{
	TransactionId fc_next_xid = XidFromFullTransactionId(fc_next_fxid);
	uint32		fc_epoch = EpochFromFullTransactionId(fc_next_fxid);

	/* 特殊交易ID。 */
	if (!TransactionIdIsNormal(fc_xid))
		return FullTransactionIdFromEpochAndXid(0, fc_xid);

	/*
	 * 64位结果必须小于等于next_fxid，因为在快照创建时next_fxid还未发布。
	 * 因此，快照中的每个TransactionId必须来自与next_fxid相同的纪元或
	 * 更早的纪元。我们知道这一点，因为next_fxid从不允许在任何快照中比TransactionIds提前超过一个纪元。
	 */
	if (fc_xid > fc_next_xid)
		fc_epoch--;

	return FullTransactionIdFromEpochAndXid(fc_epoch, fc_xid);
}

/*
 * txid比较器用于qsort/bsearch
 */
static int fc_cmp_fxid(const void *fc_aa, const void *fc_bb)
{
	FullTransactionId fc_a = *(const FullTransactionId *) fc_aa;
	FullTransactionId fc_b = *(const FullTransactionId *) fc_bb;

	if (FullTransactionIdPrecedes(fc_a, fc_b))
		return -1;
	if (FullTransactionIdPrecedes(fc_b, fc_a))
		return 1;
	return 0;
}

/*
 * 对快照的txids进行排序，以便我们可以稍后使用bsearch()。同时去除
 * 任何重复项。
 *
 * 为了保持磁盘表示的一致性，即使不使用bsearch，
 * 我们也始终进行排序。
 */
static void sort_snapshot(pg_snapshot *fc_snap)
{
	if (fc_snap->nxip > 1)
	{
		qsort(fc_snap->xip, fc_snap->nxip, sizeof(FullTransactionId), fc_cmp_fxid);
		fc_snap->nxip = qunique(fc_snap->xip, fc_snap->nxip, sizeof(FullTransactionId),
							 fc_cmp_fxid);
	}
}

/*
 * 检查fxid的可见性。
 */
static bool fc_is_visible_fxid(FullTransactionId fc_value, const pg_snapshot *fc_snap)
{
	if (FullTransactionIdPrecedes(fc_value, fc_snap->xmin))
		return true;
	else if (!FullTransactionIdPrecedes(fc_value, fc_snap->xmax))
		return false;
#ifdef USE_BSEARCH_IF_NXIP_GREATER
	else if (fc_snap->nxip > USE_BSEARCH_IF_NXIP_GREATER)
	{
		void	   *fc_res;

		fc_res = bsearch(&fc_value, fc_snap->xip, fc_snap->nxip, sizeof(FullTransactionId),
					  fc_cmp_fxid);
		/* 如果找到，交易仍在进行中 */
		return (fc_res) ? false : true;
	}
#endif
	else
	{
		uint32		fc_i;

		for (fc_i = 0; fc_i < fc_snap->nxip; fc_i++)
		{
			if (FullTransactionIdEquals(fc_value, fc_snap->xip[fc_i]))
				return false;
		}
		return true;
	}
}

/*
 * 使用StringInfo创建pg_snapshot的辅助函数。
 */

static StringInfo fc_buf_init(FullTransactionId fc_xmin, FullTransactionId fc_xmax)
{
	pg_snapshot fc_snap;
	StringInfo	fc_buf;

	fc_snap.xmin = fc_xmin;
	fc_snap.xmax = fc_xmax;
	fc_snap.nxip = 0;

	fc_buf = makeStringInfo();
	appendBinaryStringInfo(fc_buf, (char *) &fc_snap, PG_SNAPSHOT_SIZE(0));
	return fc_buf;
}

static void fc_buf_add_txid(StringInfo fc_buf, FullTransactionId fc_fxid)
{
	pg_snapshot *fc_snap = (pg_snapshot *) fc_buf->data;

	/* 在可能的realloc之前执行此操作 */
	fc_snap->nxip++;

	appendBinaryStringInfo(fc_buf, (char *) &fc_fxid, sizeof(fc_fxid));
}

static pg_snapshot *
fc_buf_finalize(StringInfo fc_buf)
{
	pg_snapshot *fc_snap = (pg_snapshot *) fc_buf->data;

	SET_VARSIZE(fc_snap, fc_buf->len);

	/* buf不再需要 */
	fc_buf->data = NULL;
	pfree(fc_buf);

	return fc_snap;
}

/*
 * 从cstring解析快照
 */
static pg_snapshot *
fc_parse_snapshot(const char *fc_str)
{
	FullTransactionId fc_xmin;
	FullTransactionId fc_xmax;
	FullTransactionId fc_last_val = InvalidFullTransactionId;
	FullTransactionId fc_val;
	const char *fc_str_start = fc_str;
	char	   *fc_endp;
	StringInfo	fc_buf;

	fc_xmin = FullTransactionIdFromU64(strtou64(fc_str, &fc_endp, 10));
	if (*fc_endp != ':')
		goto bad_format;
	fc_str = fc_endp + 1;

	fc_xmax = FullTransactionIdFromU64(strtou64(fc_str, &fc_endp, 10));
	if (*fc_endp != ':')
		goto bad_format;
	fc_str = fc_endp + 1;

	/* 应该看起来正常 */
	if (!FullTransactionIdIsValid(fc_xmin) ||
		!FullTransactionIdIsValid(fc_xmax) ||
		FullTransactionIdPrecedes(fc_xmax, fc_xmin))
		goto bad_format;

	/* 分配缓冲区 */
	fc_buf = fc_buf_init(fc_xmin, fc_xmax);

	/* 遍历值 */
	while (*fc_str != '\0')
	{
		/* 读取下一个值 */
		fc_val = FullTransactionIdFromU64(strtou64(fc_str, &fc_endp, 10));
		fc_str = fc_endp;

		/* 要求输入有序 */
		if (FullTransactionIdPrecedes(fc_val, fc_xmin) ||
			FullTransactionIdFollowsOrEquals(fc_val, fc_xmax) ||
			FullTransactionIdPrecedes(fc_val, fc_last_val))
			goto bad_format;

		/* 跳过重复项 */
		if (!FullTransactionIdEquals(fc_val, fc_last_val))
			fc_buf_add_txid(fc_buf, fc_val);
		fc_last_val = fc_val;

		if (*fc_str == ',')
			fc_str++;
		else if (*fc_str != '\0')
			goto bad_format;
	}

	return fc_buf_finalize(fc_buf);

bad_format:
	ereport(ERROR,
			(errcode(ERRCODE_INVALID_TEXT_REPRESENTATION),
			 errmsg("invalid input syntax for type %s: \"%s\"",
					"pg_snapshot", fc_str_start)));
	return NULL;				/* 保持编译器安静 */
}

/*
 * pg_current_xact_id()返回xid8
 *
 * 返回当前顶层完整交易ID。
 * 如果当前交易没有ID，则分配一个。
 */
Datum pg_current_xact_id(PG_FUNCTION_ARGS)
{
	/*
	 * 在恢复期间必须防止，因为如果未分配xid，我们会尝试
	 * 分配一个，而这将失败。程序已经依赖这个函数
	 * 始终返回有效的当前xid，因此我们不应更改为
	 * 返回NULL或类似的无效xid。
	 */
	PreventCommandDuringRecovery("pg_current_xact_id()");

	PG_RETURN_FULLTRANSACTIONID(GetTopFullTransactionId());
}

/*
 * 与pg_current_xact_id()相同，但如果尚未分配新xid则不分配。
 */
Datum pg_current_xact_id_if_assigned(PG_FUNCTION_ARGS)
{
	FullTransactionId fc_topfxid = GetTopFullTransactionIdIfAny();

	if (!FullTransactionIdIsValid(fc_topfxid))
		PG_RETURN_NULL();

	PG_RETURN_FULLTRANSACTIONID(fc_topfxid);
}

/*
 * pg_current_snapshot()返回pg_snapshot
 *
 * 返回当前快照
 *
 * 请注意，快照中仅包含顶层事务的XIDs。
 */
Datum pg_current_snapshot(PG_FUNCTION_ARGS)
{
	pg_snapshot *fc_snap;
	uint32		fc_nxip,
				fc_i;
	Snapshot	fc_cur;
	FullTransactionId fc_next_fxid = ReadNextFullTransactionId();

	fc_cur = GetActiveSnapshot();
	if (fc_cur == NULL)
		elog(ERROR, "no active snapshot set");

	/*
	 * 编译时对 procarray 的限制（MAX_BACKENDS 进程加上
	 * MAX_BACKENDS 准备的事务）保证了 nxip 不会太大。
	 */
	StaticAssertStmt(MAX_BACKENDS * 2 <= PG_SNAPSHOT_MAX_NXIP,
					 "possible overflow in pg_current_snapshot()");

	/* allocate */
	fc_nxip = fc_cur->xcnt;
	fc_snap = palloc(PG_SNAPSHOT_SIZE(fc_nxip));

	/* fill */
	fc_snap->xmin = fc_widen_snapshot_xid(fc_cur->xmin, fc_next_fxid);
	fc_snap->xmax = fc_widen_snapshot_xid(fc_cur->xmax, fc_next_fxid);
	fc_snap->nxip = fc_nxip;
	for (fc_i = 0; fc_i < fc_nxip; fc_i++)
		fc_snap->xip[fc_i] = fc_widen_snapshot_xid(fc_cur->xip[fc_i], fc_next_fxid);

	/*
	 * 我们希望它们按升序排列。这也消除了任何重复的 xids。
	 * 通常，XID 只能分配给一个后端，但在为两阶段提交准备事务时，
	 * 存在一个过渡状态，此时原始后端和为准备的事务保留的虚拟
	 * PGPROC 条目持有相同的 XID。
	 */
	sort_snapshot(fc_snap);

	/* 在排序后设置大小，因为它可能已移除重复的 xips */
	SET_VARSIZE(fc_snap, PG_SNAPSHOT_SIZE(fc_snap->nxip));

	PG_RETURN_POINTER(fc_snap);
}

/*
 * pg_snapshot_in(cstring) 返回 pg_snapshot
 *
 *	 输入函数，用于类型 pg_snapshot
 */
Datum pg_snapshot_in(PG_FUNCTION_ARGS)
{
	char	   *fc_str = PG_GETARG_CSTRING(0);
	pg_snapshot *fc_snap;

	fc_snap = fc_parse_snapshot(fc_str);

	PG_RETURN_POINTER(fc_snap);
}

/*
 * pg_snapshot_out(pg_snapshot) 返回 cstring
 *
 *	 输出函数，用于类型 pg_snapshot
 */
Datum pg_snapshot_out(PG_FUNCTION_ARGS)
{
	pg_snapshot *fc_snap = (pg_snapshot *) PG_GETARG_VARLENA_P(0);
	StringInfoData fc_str;
	uint32		fc_i;

	initStringInfo(&fc_str);

	appendStringInfo(&fc_str, UINT64_FORMAT ":",
					 U64FromFullTransactionId(fc_snap->xmin));
	appendStringInfo(&fc_str, UINT64_FORMAT ":",
					 U64FromFullTransactionId(fc_snap->xmax));

	for (fc_i = 0; fc_i < fc_snap->nxip; fc_i++)
	{
		if (fc_i > 0)
			appendStringInfoChar(&fc_str, ',');
		appendStringInfo(&fc_str, UINT64_FORMAT,
						 U64FromFullTransactionId(fc_snap->xip[fc_i]));
	}

	PG_RETURN_CSTRING(fc_str.data);
}

/*
 * pg_snapshot_recv(internal) 返回 pg_snapshot
 *
 *	 二进制输入函数，用于类型 pg_snapshot
 *
 *	 格式：int4 nxip, int8 xmin, int8 xmax, int8 xip
 */
Datum pg_snapshot_recv(PG_FUNCTION_ARGS)
{
	StringInfo	fc_buf = (StringInfo) PG_GETARG_POINTER(0);
	pg_snapshot *fc_snap;
	FullTransactionId fc_last = InvalidFullTransactionId;
	int			fc_nxip;
	int			fc_i;
	FullTransactionId fc_xmin;
	FullTransactionId fc_xmax;

	/* 加载并验证 nxip */
	fc_nxip = pq_getmsgint(fc_buf, 4);
	if (fc_nxip < 0 || fc_nxip > PG_SNAPSHOT_MAX_NXIP)
		goto bad_format;

	fc_xmin = FullTransactionIdFromU64((uint64) pq_getmsgint64(fc_buf));
	fc_xmax = FullTransactionIdFromU64((uint64) pq_getmsgint64(fc_buf));
	if (!FullTransactionIdIsValid(fc_xmin) ||
		!FullTransactionIdIsValid(fc_xmax) ||
		FullTransactionIdPrecedes(fc_xmax, fc_xmin))
		goto bad_format;

	fc_snap = palloc(PG_SNAPSHOT_SIZE(fc_nxip));
	fc_snap->xmin = fc_xmin;
	fc_snap->xmax = fc_xmax;

	for (fc_i = 0; fc_i < fc_nxip; fc_i++)
	{
		FullTransactionId fc_cur =
		FullTransactionIdFromU64((uint64) pq_getmsgint64(fc_buf));

		if (FullTransactionIdPrecedes(fc_cur, fc_last) ||
			FullTransactionIdPrecedes(fc_cur, fc_xmin) ||
			FullTransactionIdPrecedes(fc_xmax, fc_cur))
			goto bad_format;

		/* 跳过重复的 xips */
		if (FullTransactionIdEquals(fc_cur, fc_last))
		{
			fc_i--;
			fc_nxip--;
			continue;
		}

		fc_snap->xip[fc_i] = fc_cur;
		fc_last = fc_cur;
	}
	fc_snap->nxip = fc_nxip;
	SET_VARSIZE(fc_snap, PG_SNAPSHOT_SIZE(fc_nxip));
	PG_RETURN_POINTER(fc_snap);

bad_format:
	ereport(ERROR,
			(errcode(ERRCODE_INVALID_BINARY_REPRESENTATION),
			 errmsg("invalid external pg_snapshot data")));
	PG_RETURN_POINTER(NULL);	/* 保持编译器安静 */
}

/*
 * pg_snapshot_send(pg_snapshot) 返回 bytea
 *
 *	 二进制输出函数，用于类型 pg_snapshot
 *
 *	 格式：int4 nxip, u64 xmin, u64 xmax, u64 xip...
 */
Datum pg_snapshot_send(PG_FUNCTION_ARGS)
{
	pg_snapshot *fc_snap = (pg_snapshot *) PG_GETARG_VARLENA_P(0);
	StringInfoData fc_buf;
	uint32		fc_i;

	pq_begintypsend(&fc_buf);
	pq_sendint32(&fc_buf, fc_snap->nxip);
	pq_sendint64(&fc_buf, (int64) U64FromFullTransactionId(fc_snap->xmin));
	pq_sendint64(&fc_buf, (int64) U64FromFullTransactionId(fc_snap->xmax));
	for (fc_i = 0; fc_i < fc_snap->nxip; fc_i++)
		pq_sendint64(&fc_buf, (int64) U64FromFullTransactionId(fc_snap->xip[fc_i]));
	PG_RETURN_BYTEA_P(pq_endtypsend(&fc_buf));
}

/*
 * pg_visible_in_snapshot(xid8, pg_snapshot) 返回 bool
 *
 *	 事务 ID 在快照中是否可见？
 */
Datum pg_visible_in_snapshot(PG_FUNCTION_ARGS)
{
	FullTransactionId fc_value = PG_GETARG_FULLTRANSACTIONID(0);
	pg_snapshot *fc_snap = (pg_snapshot *) PG_GETARG_VARLENA_P(1);

	PG_RETURN_BOOL(fc_is_visible_fxid(fc_value, fc_snap));
}

/*
 * pg_snapshot_xmin(pg_snapshot) 返回 xid8
 *
 *	 返回快照的 xmin
 */
Datum pg_snapshot_xmin(PG_FUNCTION_ARGS)
{
	pg_snapshot *fc_snap = (pg_snapshot *) PG_GETARG_VARLENA_P(0);

	PG_RETURN_FULLTRANSACTIONID(fc_snap->xmin);
}

/*
 * pg_snapshot_xmax(pg_snapshot) 返回 xid8
 *
 *	 返回快照的 xmax
 */
Datum pg_snapshot_xmax(PG_FUNCTION_ARGS)
{
	pg_snapshot *fc_snap = (pg_snapshot *) PG_GETARG_VARLENA_P(0);

	PG_RETURN_FULLTRANSACTIONID(fc_snap->xmax);
}

/*
 * pg_snapshot_xip(pg_snapshot) 返回 setof xid8
 *
 *	 返回快照中的进行中 xid8。
 */
Datum pg_snapshot_xip(PG_FUNCTION_ARGS)
{
	FuncCallContext *fc_fctx;
	pg_snapshot *fc_snap;
	FullTransactionId fc_value;

	/* 在第一次调用时初始化 fctx，并获取快照的副本 */
	if (SRF_IS_FIRSTCALL())
	{
		pg_snapshot *fc_arg = (pg_snapshot *) PG_GETARG_VARLENA_P(0);

		fc_fctx = SRF_FIRSTCALL_INIT();

		/* 复制用户快照 */
		fc_snap = MemoryContextAlloc(fc_fctx->multi_call_memory_ctx, VARSIZE(fc_arg));
		memcpy(fc_snap, fc_arg, VARSIZE(fc_arg));

		fc_fctx->user_fctx = fc_snap;
	}

	/* 一次返回一个值 */
	fc_fctx = SRF_PERCALL_SETUP();
	fc_snap = fc_fctx->user_fctx;
	if (fc_fctx->call_cntr < fc_snap->nxip)
	{
		fc_value = fc_snap->xip[fc_fctx->call_cntr];
		SRF_RETURN_NEXT(fc_fctx, FullTransactionIdGetDatum(fc_value));
	}
	else
	{
		SRF_RETURN_DONE(fc_fctx);
	}
}

/*
 * 报告最近的事务 ID 的状态，如果是已封装的、
 * 截断或其他过旧的 XIDs，则返回 null。
 *
 * 传入的时期限定的 xid 被视为普通的 xid，而不是
 * multixact id。
 *
 * 如果它指向一个已提交的子事务，结果是子事务状态，即使
 * 父事务仍然在进行中或可能已经中止。
 */
Datum pg_xact_status(PG_FUNCTION_ARGS)
{
	const char *fc_status;
	FullTransactionId fc_fxid = PG_GETARG_FULLTRANSACTIONID(0);
	TransactionId fc_xid;

	/*
	 * 我们必须防止因并发截断 clog 条目而导致的
	 * SLRU 查找 I/O 错误。
	 */
	LWLockAcquire(XactTruncationLock, LW_SHARED);
	if (fc_TransactionIdInRecentPast(fc_fxid, &fc_xid))
	{
		Assert(TransactionIdIsValid(fc_xid));

		/*
		 * 就像在对行进行可见性检查时，在查看 CLOG
		 * 之前检查事务是否仍在进行中。否则，我们会错误地
		 * 返回 "已提交" 对于一个正在提交并已更新 CLOG 的事务，
		 * 但尚未从 proc array 中移除其 XID。（请参阅在
		 * heapam_visibility.c 顶部对此竞争条件的说明）
		 */
		if (TransactionIdIsInProgress(fc_xid))
			fc_status = "in progress";
		else if (TransactionIdDidCommit(fc_xid))
			fc_status = "committed";
		else
		{
			/* 它必须已经中止或崩溃 */
			fc_status = "aborted";
		}
	}
	else
	{
		fc_status = NULL;
	}
	LWLockRelease(XactTruncationLock);

	if (fc_status == NULL)
		PG_RETURN_NULL();
	else
		PG_RETURN_TEXT_P(cstring_to_text(fc_status));
}
